package cn.edu.hit.database;

import cn.edu.hit.core.Document;
import cn.edu.hit.core.db.DataReader;
import cn.edu.hit.core.db.Filters;
import com.jayway.jsonpath.*;
import net.minidev.json.JSONObject;
import net.minidev.json.JSONValue;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class FileDatabase implements DataReader {
    //    private Path path;
//    private BufferedReader reader;
    private List<String> lines;

    public FileDatabase(Path path) throws IOException {
        this(Files.newBufferedReader(path));
    }

    public FileDatabase(InputStream inputStream) {
        this(new BufferedReader(new InputStreamReader(inputStream)));
    }

    public FileDatabase(BufferedReader reader) {
        lines = reader.lines().collect(Collectors.toList());
    }

    public Stream<String> lines() {
        return lines.stream();
    }

    @Override
    public Document getById(String _id) {
        JsonPath jsonPath = JsonPath.compile("$._id.$oid");
        return lines().filter(s -> jsonPath.read(s).equals(_id)).findAny()
                .map(FileDatabase::parseDocument)
                .orElse(null);
    }

    private static Document parseDocument(String json) {
        JSONObject jsonObject = (JSONObject) JSONValue.parse(json);
        Document document = new Document(jsonObject);
        Object _id = document.get("_id");
        if (_id != null && _id instanceof Map) {
            document.put("_id", ((Map) _id).get("$oid"));
        }
        return document;
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        FileDatabase database = new FileDatabase(Paths.get("D:\\毕设项目\\new-program\\New-Inference-engine\\simple-program\\conf\\mongo.data"));

        Document document = database.getById("5c3d700b9b294f2110d8099b");
        System.out.println(document);

        database.queryByTemplate(Filters.and(
                Filters.eq("name", "5c3d700b9b294f2110d8099b"),
                Filters.eq("dataSourceArgs", "GEN03001"),
                Filters.eq("type", "signal")))
                .forEach(System.out::println);
    }

    public List<Document> queryByPath(JsonPath jsonPath) throws IOException {
        Configuration conf = Configuration.defaultConfiguration();
        conf.addOptions(Option.ALWAYS_RETURN_LIST, Option.DEFAULT_PATH_LEAF_TO_NULL);
        String text = "{\"data\":[" + lines().collect(Collectors.joining(",")) + "]}";
        List<Object> objects = Optional.ofNullable(text).map(s -> {
            List x = JsonPath.using(conf).parse(s).read(jsonPath, List.class);
            return x;
        }).orElse(Collections.emptyList());
        return objects.stream()
                .map(o -> {
                    Document document = new Document((Map) o);
                    Object _id = document.get("_id");
                    if (_id != null && _id instanceof Map) {
                        document.put("_id", ((Map) _id).get("$oid"));
                    }
                    return document;
                })
//                .map(json -> parseDocument(((String) json)))
                .collect(Collectors.toList());
    }

    @Override
    public List<Document> queryByTemplate(Map<String, Object> template) {
        try {
            return queryByPath(fromDocumentQuery(template));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

    public static JsonPath fromDocumentQuery(Map<String, Object> query) {
        if (query == null) {
            return JsonPath.compile("$");
        }

        Filter filter = query.entrySet().stream().map(entry -> makeFilter(entry.getKey(), entry.getValue(), "@"))
                .reduce((p1, p2) -> p1.and(p2))
                .orElse(null);
        return JsonPath.compile("$.data" + filter);
    }

    private static Filter makeFilter(String key, Object value, String parentPath) {
        if (key.startsWith("$")) {
            switch (key) {
                case "$eq":
                    return Filter.filter(Criteria.where(parentPath).eq(value));
                case "$gt":
                    return Filter.filter(Criteria.where(parentPath).gt(value));
                case "$gte":
                    return Filter.filter(Criteria.where(parentPath).gte(value));
                case "$in":
                    return Filter.filter(Criteria.where(parentPath).in((Collection<?>) value));
                case "$lt":
                    return Filter.filter(Criteria.where(parentPath).lt(value));
                case "$lte":
                    return Filter.filter(Criteria.where(parentPath).lte(value));
                case "$ne":
                    return Filter.filter(Criteria.where(parentPath).ne(value));
                case "$nin":
                    return Filter.filter(Criteria.where(parentPath).nin(value));

                case "$and": {
                    List<Map<String, Object>> list;
                    if (value instanceof Map) {
                        list = Collections.singletonList(((Map) value));
                    } else {
                        list = (List<Map<String, Object>>) value;
                    }
                    return list
                            .stream()
                            .map(mp -> mp.entrySet().stream().findFirst().map(entry ->
                                    makeFilter(entry.getKey(), entry.getValue(), parentPath)).orElse(null))
                            .reduce(Filter::and)
                            .orElse(null);
                }
                case "$or": {
                    List<Map<String, Object>> list;
                    if (value instanceof Map) {
                        list = Collections.singletonList(((Map) value));
                    } else {
                        list = (List<Map<String, Object>>) value;
                    }
                    return list
                            .stream()
                            .map(mp -> mp.entrySet().stream().findFirst().map(entry ->
                                    makeFilter(entry.getKey(), entry.getValue(), parentPath)).orElse(null))
                            .reduce(Filter::or)
                            .orElse(null);
                }
                case "$not":
                    return ((Map<String, Object>) value).entrySet()
                            .stream()
                            .map(entry -> makeFilter(entry.getKey(), entry.getValue(), parentPath))
                            .findFirst()
                            .map(filter -> Filter.filter(x -> !filter.apply(x)))
                            .orElse(null);
                case "$nor": {
                    List<Map<String, Object>> list;
                    if (value instanceof Map) {
                        list = Collections.singletonList(((Map) value));
                    } else {
                        list = (List<Map<String, Object>>) value;
                    }
                    return list
                            .stream()
                            .map(mp -> mp.entrySet().stream().findFirst().map(entry ->
                                    makeFilter(entry.getKey(), entry.getValue(), parentPath)).orElse(null))
                            .reduce(Filter::or)
                            .map(filter -> Filter.filter(x -> !filter.apply(x)))
                            .orElse(null);
                }
            }
        }

        if (value instanceof Map) {
            return ((Map<String, Object>) value).entrySet()
                    .stream()
                    .map(entry -> makeFilter(entry.getKey(), entry.getValue(), parentPath + "." + key))
                    .reduce(Filter::and)
                    .orElse(null);
        } else {
            return Filter.filter(Criteria.where(key).eq(value));
        }
    }
}
